/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ar.edu.utn.kohonen.main;

import java.io.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
import org.generation5.*;
import org.generation5.nn.*;
import org.generation5.util.*;
import org.generation5.vision.*;

/**
 *
 * @author James Matthews
 */
public class KohonenImageClassifier implements Visualizable {
    public static int networkWidth = 4;
    public static int networkHeight = 5;
    public static int subImages = 36;
    public static int numberFeatures = subImages * 8 + 8;
    
    protected BufferedImage[] inputImages;
    protected int numberImages = 0;
    protected File[] imageFiles;
    protected double[][] featureVectors;
    KohonenNN kohonenNetwork;
    
    public void setNetworkSize(int width, int height) 
    {
        networkWidth = width;
        networkHeight = height;
    }
    
    public int getNetworkWidth()
    {
        return networkWidth;
    }
    
    public int getNetworkHeight()
    {
        return networkHeight;
    }
    
    public void setSubImages(int si)
    {
        subImages = si;
    }
    
    public int getSubImages()
    {
        return subImages;
    }
    
    public int getNumberFeatures()
    {
        return numberFeatures;
    }
    
    public void loadImages(String directory)
    {
        File dir = new File(directory);
        imageFiles = dir.listFiles(new FileFilter() {

            public boolean accept(File f) {
                return f.isFile() && f.getName().endsWith(".jpg");
            }
        });

        numberImages = imageFiles.length;
        inputImages = new BufferedImage[numberImages];

        System.err.println("Loading " + numberImages + " files...");
        for (int i = 0; i < numberImages; i++) {
            try {
                inputImages[i] = ImageIO.read(imageFiles[i]);
            } catch (Exception e) {
                System.err.println("  - " + imageFiles[i].getName() + " failed to load.");
                continue;
            }
            System.err.println("  - " + imageFiles[i].getName());
        }
    }
    
    protected BufferedImage[] generateSubImages(BufferedImage img, int padding)
    {
        BufferedImage[] subImage = new BufferedImage[subImages];
        int subWidth = (int)Math.sqrt(subImages);
        int index = 0;
        int hh = 0, ho = 0;
        int siw = img.getWidth() / subWidth;
        int sih = img.getHeight() / subWidth;

        for (int y = 0; y < subWidth; y++) {
            if (y == 0) {
                ho = 0;
                hh = padding;
            } else if (y == subWidth - 1) {
                ho = padding;
                hh = padding;
            } else {
                ho = padding;
                hh = padding * 2;
            }
            for (int x = 0; x < subWidth; x++) {
                if (x == 0) {
                    subImage[index++] = img.getSubimage(x * siw, y * sih - ho,
                            siw + padding, sih + hh);
                } else if (x == subWidth - 1) {
                    subImage[index++] = img.getSubimage(x * siw - padding, y * sih - ho,
                            siw + padding, sih + hh);
                } else {
                    subImage[index++] = img.getSubimage(x * siw - padding, y * sih - ho,
                            siw + 2 * padding, sih + hh);
                }
            }
        } 
        
        return subImage;
    }
    
    public void generateVectors()
    {
        if (inputImages == null)
            throw new NullPointerException("no input images loaded!");
        
        System.err.print("Generating vectors...");
        featureVectors = new double[numberImages][numberFeatures];

        double[] data;
        double[][] lines;

        Histogram hist;
        BufferedImage[] subImage = new BufferedImage[subImages];
        HoughImageClassifier hic = new HoughImageClassifier();
        hic.setLocalPeakNeighbourhood(25);
        
        int width = inputImages[0].getWidth();
        int height = inputImages[0].getHeight();
        
//        TimeSeries ts = new TimeSeries();
//        ts.setDimensions(subWidth, subWidth);

        for (int i = 0; i < numberImages; i++) {
            hist = new Histogram(inputImages[i]);
            data = hist.getMean();

            featureVectors[i][0] = data[0] / 255.0;
            featureVectors[i][1] = data[1] / 255.0;
            featureVectors[i][2] = data[2] / 255.0;

            data = hist.getStandardDeviation();

            featureVectors[i][3] = data[0] / 255.0;
            featureVectors[i][4] = data[1] / 255.0;
            featureVectors[i][5] = data[2] / 255.0;
            
            subImage = generateSubImages(inputImages[i], 10);
            
            for (int j = 0; j < subImages; j++) {
                hist = new Histogram(subImage[j]);
                data = hist.getMean();

                featureVectors[i][6 + (j * 8)] = data[0] / 255.0;
                featureVectors[i][7 + (j * 8)] = data[1] / 255.0;
                featureVectors[i][8 + (j * 8)] = data[2] / 255.0;

                data = hist.getStandardDeviation();

                featureVectors[i][9  + (j * 8)] = data[0] / 255.0;
                featureVectors[i][10 + (j * 8)] = data[1] / 255.0;
                featureVectors[i][11 + (j * 8)] = data[2] / 255.0;
                
                lines = hic.getLines(subImage[j], 1);
                
                featureVectors[i][12 + (j * 8)] = lines[0][0];
                featureVectors[i][13 + (j * 8)] = lines[0][1];
                
/*                if (i == 6) {
                    hic.setLineColor(Color.RED);
                    try {
                        ts.addSnapshot(hic.getSuperimposed(subImage[j], 1.0));
                    } catch (Exception e) {
                        ;
                    }
                }*/
            }

            int last = 13 + ((subImages - 1) * 8) + 1;
            lines = hic.getLines(inputImages[i], 1);
            
            featureVectors[i][last]   = lines[0][0];
            featureVectors[i][last+1] = lines[0][1];
        }
        
//        ts.writeImage("kohonenHough.png", width*subWidth, height*subWidth);
        
        System.err.println("done.");
    }
    
    public void trainNetwork()
    {
        if (featureVectors == null || inputImages == null)
            throw new NullPointerException("either no input images or no features created.");
        
        System.err.print("Training neural network...");

        kohonenNetwork = new KohonenNN(networkWidth, networkHeight, numberFeatures);
        KohonenImageTrainer train = new KohonenImageTrainer();
        train.featureVectors = featureVectors;
        train.setNetwork(kohonenNetwork);
        train.init();
        train.setPhases(inputImages.length * 15, inputImages.length * 5);

        kohonenNetwork.initialize(0.0, 1.0);

        do {
            train.doStep();
        } while (!train.isComplete());

        System.err.println("done.");        
    }

    @Override
    public void render(Graphics g, int w, int h) {
        int width  = inputImages[0].getWidth();
        int height = inputImages[0].getHeight(); 
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, w, h);

        int[][] neuronStep = new int[networkWidth][networkHeight];
        int[] neuron;
        int nx, ny;
        for (int i = 0; i < featureVectors.length; i++) {
            neuron = kohonenNetwork.getClosestNeuron(featureVectors[i]);
            nx = neuron[0];
            ny = neuron[1];

            if (neuronStep[nx][ny] == 0) {
                g.drawImage((Image) inputImages[i], nx * width, ny * height, width / 3, height / 3, null);
            } else if (neuronStep[nx][ny] == 1) {
                g.drawImage((Image) inputImages[i], nx * width + width / 3, ny * height, width / 3, height / 3, null);
            } else if (neuronStep[nx][ny] == 2) {
                g.drawImage((Image) inputImages[i], nx * width + 2 * (width / 3), ny * height, width / 3, height / 3, null);                
            } else if (neuronStep[nx][ny] == 3) {
                g.drawImage((Image) inputImages[i], nx * width, ny * height + height / 3, width / 3, height / 3, null);
            } else if (neuronStep[nx][ny] == 4) {
                g.drawImage((Image) inputImages[i], nx * width + width / 3, ny * height + height / 3, width / 3, height / 3, null);                
            } else if (neuronStep[nx][ny] == 5) {
                g.drawImage((Image) inputImages[i], nx * width + 2 * (width / 3), ny * height + height / 3, width / 3, height / 3, null);                
            } else if (neuronStep[nx][ny] == 6) {
                g.drawImage((Image) inputImages[i], nx * width, ny * height + 2 * (height / 3), width / 3, height / 3, null);
            } else if (neuronStep[nx][ny] == 7) {
                g.drawImage((Image) inputImages[i], nx * width + width / 3, ny * height + 2 * (height / 3), width / 3, height / 3, null);                
            } else if (neuronStep[nx][ny] == 8) {
                g.drawImage((Image) inputImages[i], nx * width + 2 * (width / 3), ny * height + 2 * (height / 3), width / 3, height / 3, null);                
            }

            neuronStep[nx][ny]++;
        }
    }

    @Override
    public void writeImage(String s, int width, int height) {
        try {
            ImageHelper.writeVisualizedImage(s, width, height, this);
        } catch (IOException e) {
            System.err.println("Error writing image.");
        }
    }
    
    public void writeImage(String s)
    {
        int width  = inputImages[0].getWidth();
        int height = inputImages[0].getHeight(); 
        
        writeImage(s, networkWidth * width, networkHeight * height);        
    }
    
    public void findSimilarImages(String s)
    {
        File dir = new File(s);
        BufferedImage img;
        try {
            img = ImageIO.read(dir);
        } catch (Exception e) {
            System.err.println("input image failed to load.");
            return;
        }
        
        int subWidth = (int)Math.sqrt(subImages);
        double[] features = new double[numberFeatures];
        double[][] lines;
        
        BufferedImage[] subImage = new BufferedImage[subImages];
        HoughImageClassifier hic = new HoughImageClassifier();
        Histogram hist = new Histogram(img);
        
        double[] data = hist.getMean();
        int width = img.getWidth();
        int height = img.getHeight();

        features[0] = data[0] / 255.0;
        features[1] = data[1] / 255.0;
        features[2] = data[2] / 255.0;

        data = hist.getStandardDeviation();

        features[3] = data[0] / 255.0;
        features[4] = data[1] / 255.0;
        features[5] = data[2] / 255.0;

        // Generate subimages with padding
        int index = 0;
        int padding = 10;
        int hh = 0, ho = 0;
        int sih = height / subWidth;
        int siw = width / subWidth;

        for (int y = 0; y < subWidth; y++) {
            if (y == 0) {
                ho = 0;
                hh = padding;
            } else if (y == subWidth - 1) {
                ho = padding;
                hh = padding;
            } else {
                ho = padding;
                hh = padding * 2;
            }
            for (int x = 0; x < subWidth; x++) {
                if (x == 0) {
                    subImage[index++] = img.getSubimage(x * siw, y * sih - ho,
                            siw + padding, sih + hh);
                } else if (x == subWidth - 1) {
                    subImage[index++] = img.getSubimage(x * siw - padding, y * sih - ho,
                            siw + padding, sih + hh);
                } else {
                    subImage[index++] = img.getSubimage(x * siw - padding, y * sih - ho,
                            siw + 2 * padding, sih + hh);
                }
            }
        }

        for (int j = 0; j < subImages; j++) {
            hist = new Histogram(subImage[j]);
            data = hist.getMean();

            features[6 + (j * 8)] = data[0] / 255.0;
            features[7 + (j * 8)] = data[1] / 255.0;
            features[8 + (j * 8)] = data[2] / 255.0;

            data = hist.getStandardDeviation();

            features[9 + (j * 8)] = data[0] / 255.0;
            features[10 + (j * 8)] = data[1] / 255.0;
            features[11 + (j * 8)] = data[2] / 255.0;

            lines = hic.getLines(subImage[j], 1);

            features[12 + (j * 8)] = lines[0][0];
            features[13 + (j * 8)] = lines[0][1];
        }

        int last = 13 + ((subImages - 1) * 8) + 1;
        lines = hic.getLines(img, 1);

        features[last] = lines[0][0];
        features[last + 1] = lines[0][1];

        int[] neuron = kohonenNetwork.getClosestNeuron(features), in;
        for (int i=0; i<inputImages.length; i++) {
            in = kohonenNetwork.getClosestNeuron(featureVectors[i]);
            if (in[0] == neuron[0] && in[1] == neuron[1]) {
                System.out.println("Matching image with " + imageFiles[i].getName());
            }
        }
    }

    public static void main(String[] args) {
        KohonenImageClassifier kic = new KohonenImageClassifier();
        
        kic.loadImages("/home/alejandro/Imágenes/thumbs");
        kic.generateVectors();
        kic.trainNetwork();
        kic.writeImage("kohonenOutput.jpg");
        
        kic.findSimilarImages("/home/alejandro/Imágenes/00124560_w.jpg");
       // kic.findSimilarImages("/home/alejandro/Imágenes/casconegrobuscar.jpg");
        System.out.println("TERMINO TODO");
    }

    static public class KohonenImageTrainer extends KohonenTrainer {

        public double[][] featureVectors;
        private int currentStep = 0;

        public double[] getTrainingPoint() {
            return featureVectors[currentStep++ % featureVectors.length];
        }
    }
    
    static public class HoughImageClassifier extends LineHoughTransformOp {
        public double[][] getLines(BufferedImage img, int numberLines) {
            double[][] lines = new double[numberLines][2];
            int index = 0;
            
            GreyscaleFilter s1 = new GreyscaleFilter();
            SobelEdgeDetectorFilter s2 = new SobelEdgeDetectorFilter();
            ThresholdFilter s3 = new ThresholdFilter();
            
            BufferedImage in1 = s1.filter(img);    // to greyscale
            BufferedImage in2 = s2.filter(in1);
            BufferedImage in3 = s3.filter(in2);
            
            double accRatio = 1.0d;
            
            setLocalPeakNeighbourhood(25);
            run(in3);

            maxAccValue = getMaximum();

            long thresh = (int) (accRatio * maxAccValue);

            int tmp = Math.max(img.getWidth(), img.getHeight());
            int hh = (int) (Math.sqrt(2) * tmp);
            int hw = 180;

            for (int i = 0; i < hw; i++) {
                for (int j = 0; j < 2 * hh; j++) {

                    if (houghAccumulator[i][j] >= thresh) {
                        // This is HORRIBLY inefficient, if another local peak is
                        // found,then that is going to be the line that needs to
                        // be drawn...why check it hundreds more times?
                        //if (localPeak(i, j, hw, hh, localPeakNeighbourhood) == false)
                        //    continue;
                             
                        lines[index][0] = Math.sin(i * thetaStep);
                        lines[index][1] = Math.cos(i * thetaStep);
                        index++;
                    }
                    
                    if (index == numberLines) break;
                }
                
                if (index == numberLines) break;
            }
            
            
            return lines;
        }
    }
}
